from abc import abstractmethod, ABCMeta, abstractproperty
from django.contrib.auth.models import User
from spargameproject.settings import GAME_ENGINE_PLAY_HANDLERS, GAME_BOTS
from spargameproject.spargame.models import Play, PlayMetaData, Game, GamePlayer
import traceback
from spargameproject.spargame.templatetags.custom_filters import getAllMetadata

class GameBot(object):
  __metaclass__ = ABCMeta
  @abstractmethod
  def handleResponse(self, game_engine, game, game_player, play_type):
    pass
  @abstractproperty
  def getType(self):
    pass

class PlayHandler(object):
  __metaclass__ = ABCMeta
  @abstractmethod
  def handlePlay(self, play, game_engine):
    pass
  @abstractproperty
  def getType(self):
    pass

class NoOpPlayHandler(PlayHandler):
  def __init__(self, play_type):
    self.type = play_type
  def getType(self):
    return self.type

  def handlePlay(self, play, game_engine):
    return [], {}

class GameEngine(object):

  def __init__(self):
    self.play_handler_map = {'add-friend': [NoOpPlayHandler('add-friend')],
                             'chat_message': [NoOpPlayHandler('add-friend')]}
    self.bot_map = {}
    for play_handler in GAME_ENGINE_PLAY_HANDLERS:
      play_handler_instance = getInstanceFromString(play_handler)

      play_type = play_handler_instance.getType()
      if not play_type in self.play_handler_map:
        self.play_handler_map[play_type] = []

      self.play_handler_map[play_type].append(play_handler_instance)

    for bot in GAME_BOTS:
      game_bot_instance = getInstanceFromString(bot)
      bot_type = game_bot_instance.getType()
      self.bot_map[bot_type] = game_bot_instance

  def getBot(self):
    return User.objects.get_or_create(id= -1)[0]

  def handleBotPlays(self, response, game, play_type):
    turn_player_id = game.getMetadata('turn_player')
    turn_game_player = GamePlayer.objects.get(player=turn_player_id, game=game)
    if turn_game_player.getMetadata('is_bot'):
      bot = self.bot_map[turn_game_player.getMetadata('bot_type')]
      if 'bot_plays' not in response:
        response['bot_plays'] = []
      bot_play = bot.handleResponse(self, game, turn_game_player, play_type)
      if bot_play:
        bot_errors, bot_response = self.handlePlay(bot_play)
        if bot_errors:
          print bot_errors
          updated_bot_errors = response['errors'] if 'errors' in response else []
          updated_bot_errors.extend(bot_errors)
          response['errors'] = updated_bot_errors
          return
        response['bot_plays'].append(
             {'player_id': str(turn_game_player.player.id),
              'player_name': turn_game_player.player.name,
              'play_type': 'drop',
              'metadata': getAllMetadata(bot_play)})
        self.handleBotPlays(response, game, play_type)

  def makePlay(self, medadata_array, game_id, player, play_type):
    play = self.playFromInputs(medadata_array, game_id, player, play_type)
    errors = self.getPlayValidationErrors(play)
    response = None
    if not errors:
      try:
        errors, response = self.handlePlay(play)
        if game_id != "0":
          game = Game.objects.get(id=game_id)
          self.handleBotPlays(response, game, play_type)
          print response
      except:
        traceback.print_exc()
        errors.append('Error while processing play. Please try again')
    return {'errors': errors, 'response': response, 'play_id': play.id}

  def playFromInputs(self, metadata_array, game_id, player, play_type):
    game = None
    if game_id != "0":
      game = Game.objects.get(pk=game_id)

    play = Play.objects.create(player=player, game=game, play_type=play_type)

    metadata = dict([item.split(':') for item in metadata_array])
    for key, value in metadata.iteritems():
      play_metadata = PlayMetaData(play=play, data_key=key, data_value=value)
      play.playmetadata_set.add(play_metadata)
    return play

  def getPlayValidationErrors(self, play):
    errors = []
    game = play.game
    if game:
      player = play.player
      game_player = GamePlayer.objects.get(game=game, player=player)
      if not game.isActivePlayer(game_player):
        errors.append(
            '%s is not a member of this game and so cannot make a play' % player.name)
    return errors

  def handlePlay(self, play):
    errors = []
    response = {}
    if play.play_type not in self.play_handler_map:
      errors.append('No handler defined for play type: %s' % play.play_type)
    else:
      for handler in self.play_handler_map[play.play_type]:
        play_errors, play_response = handler.handlePlay(play, self)
        errors.extend(play_errors)
        response.update(play_response)
    return errors, response

  def initGame(self, game):
    init_game_play = Play.objects.create(play_type='init_game', game=game, player=game.creator_player)
    self.handlePlay(init_game_play)

def getInstanceFromString(class_string):
  class_string_split = class_string.rpartition('.')
  mod = __import__(class_string_split[0], fromlist=[class_string_split[2]])
  class_name = getattr(mod, class_string_split[2])
  class_instance = class_name()
  return class_instance
